home *** CD-ROM | disk | FTP | other *** search
/ ETO Development Tools 1 / ETO Development Tools 1.iso / Essentials / Developer Essentials Jul 90 / DTS Sample Code / Macintosh Sample Code / SC.009.FracApp300 / UFracApp300.inc1.p < prev    next >
Encoding:
Text File  |  1988-08-17  |  70.3 KB  |  1,658 lines  |  [TEXT/MPS ]

  1. {-------------------------------------------------------------
  2. #
  3. #    Apple Macintosh Developer Technical Support
  4. #
  5. #    MacApp Color QuickDraw Fractal Printing Sample Application
  6. #
  7. #    FracApp300
  8. #
  9. #    UFracApp300.inc1.p    -    Pascal Source
  10. #
  11. #    Copyright © 1988 Apple Computer, Inc.
  12. #    All rights reserved.
  13. #
  14. #    Versions:    1.0                    8/88
  15. #
  16. #    Components:    MFracApp300.p        August 1, 1988
  17. #                UFracApp300.p        August 1, 1988
  18. #                UFracApp300.inc1.p    August 1, 1988
  19. #                FracApp300.r        August 1, 1988
  20. #                FracApp300.make        August 1, 1988
  21. #
  22. #    The FracApp300 program is a version of the FracApp program that is
  23. #    set up to be as compatible as possible.  It uses the PrGeneral call
  24. #    in order to print at high resolution.  It demonstrates how to create
  25. #    and save 300 dpi PICT files.  It uses an offscreen port to calculate
  26. #    the data, and CopyBits to update the window on the screen.  When the
  27. #    documents are written or read, the QuickDraw bottlenecks are used to
  28. #    avoid having a huge memory hit during saving or opening. (800K of RAM
  29. #    not needed, on a 640x480 screen, a big win.)  The Palette Manager is
  30. #    used very slightly, only to associate a small palette with only Black
  31. #    and White with each window.  This avoids needing the system palette
  32. #    for each window, when no colors are used.  Since we are printing to
  33. #    normal printers, we only use B&W in this version.
  34. #            Written in MacApp Object Pascal code.
  35. #            Compatibility rating = 1.  (The use of PrGeneral is slightly 
  36. #                out of the ordinary, although supported.)
  37. #
  38. #    The program is a complete Macintosh application written in Object 
  39. #    Pascal using MacApp.  It supports multiple windows, calculations in the
  40. #    background under MultiFinder, use of the Palette Manager, reading and
  41. #    writing of PICT files using the bottlenecks, and shows how to calculate
  42. #    the Mandelbrot set.
  43. #
  44. #    There is a resource file that is necessary as well, to define the Menus, Window,
  45. #    Dialog, and Palette resources used in the program.  
  46. -------------------------------------------------------------}
  47. {FracApp300:
  48.     Copyright 1988 by Bob.  All rights reserved, since Bob has all rights.
  49.     February 1, 1988.
  50.     Updated May 1, 1988:  removed the color handling stuff, turned it into
  51.         a printing example with 300 dpi printing of B&W mandelbrots.
  52.  
  53.     Written by Bo3b Johnson of Developer Technical Support. }
  54.  
  55. { Version 1.1:
  56.     8/1/88: Fixed the print handler to handle printing from the Finder (oops!).  The
  57.         handler was rearranged to more closely follow the style of the StdPrintHandler. }
  58.  
  59.  
  60. { The following is a list of features or bug fixes that could be added to the program:
  61.   *** Add a menu option to scale to 72 DPI or not.
  62.   *** Check the segmentation.
  63.   *** Bug with selection rectangle on edge of document, moving by 1 pixel.
  64.   *** Make it run crashless using temp documents to store partial fractals.
  65.   *** Updates could be cleaner so no partial fractals are displayed.
  66.   *** Override window.updateevent so we can avoid the EraseRect on updates.
  67.   *** Draw selection rect in offscreen, copy up to screen for flicker free selection.
  68.   *** Crash on 3 monitor system during window drag.
  69.   *** Could set the bytes directly in offscreen PixMap, skip using MoveTo:Line.
  70.   *** Copy up from small picture up to big screen gets garbage, src rect too big?
  71.   *** Allow a way to Zoom in using coordinates.
  72.   *** Bigger penSize for fast, lo-res fractals.  Allow user to set size of pen.
  73.   *** Add MacApp debugging stuff like Inspect and TList object checking.
  74.   *** Some things for the reader to do to modify the program.
  75.   }
  76.  
  77.  
  78. { Where does it fit:
  79.     This is a series of sample programs for those doing development
  80.     using Color QuickDraw.  Since the whole color problem depends
  81.     upon the exact effect desired, there are a number of answers
  82.     to how to use colors, from the simple to the radically complex.
  83.     These programs try to cover the gamut, so you should use 
  84.     which ever seems appropriate.  In most cases, use the simplest
  85.     one that will give the desired results.  The compatibility
  86.     rating is from 0..9 where low is better.  The more known risks 
  87.     there are the higher the rating.
  88.     
  89.     
  90.     The programs (in order of compatibility):
  91.     
  92.         SillyBalls:
  93.             This is the simplest use of Color QuickDraw, and does
  94.             not use the Palette Manager.  It draws randomly colored
  95.             balls in a color window.  This is intended to give you
  96.             the absolute minimum required to get color on the screen.
  97.             Written in straight Pascal code.
  98.             Compatibility rating = 0, no known risks.
  99.         
  100.         FracAppPalette:
  101.             This is a version of FracApp that uses only the Palette
  102.             Manager.  It does not support color table animation
  103.             since that part of the Palette Manager is not sufficient.
  104.             The program demonstrates a full color palette that is
  105.             used to display the Mandelbrot set.  It uses an offscreen
  106.             gDevice w/ Port to handle the data, using CopyBits to
  107.             draw to the window.  The Palette is automatically 
  108.             associated with each window.  The PICT files are read
  109.             and written using the bottlenecks, to save on memory
  110.             useage.
  111.             Written in MacApp Object Pascal code.
  112.             Compatibility rating = 0, no known risks.
  113.         
  114.         TubeTest:
  115.             This is a small demo program that demonstrates using the
  116.             Palette Manager for color table animation.  It uses a 
  117.             color palette with animating entries, and draws using the
  118.             Palette Manager.  There are two circles of animating colors
  119.             which gives a flowing tube effect.  This is a valid case
  120.             for using the animating colors aspect of the Palette Manager,
  121.             since the image is being drawn directly.
  122.             Written in straight Pascal code.
  123.             Compatibility rating = 0, no known risks.
  124.         
  125.         FracApp:
  126.             This is the ‘commercial quality’ version of FracApp.  This
  127.             version supports color table animation, using an offscreen
  128.             gDevice w/ Port, and handles multiple documents.  The
  129.             CopyBits updates to the screen are as fast as possible.  The
  130.             program does not use the Palette Manager, except to
  131.             provide for the system palette, or color modes with less than
  132.             255 colors.  For color table animation using an offscreen
  133.             gDevice w/ Port, it uses the Color Manager and handles the
  134.             colors itself.  Strict compatibility was relaxed to allow for
  135.             a higher performance program.  This is the most ‘real’ of the
  136.             sample programs.
  137.             Written in MacApp Object Pascal code.
  138.             Compatibility rating = 2.  (nothing will break, but it may not
  139.                 always look correct.)
  140.         
  141.         FracApp300:    (***)
  142.             This doesn't support colors, but demonstrates how to create and
  143.             use a 300 dpi bitmap w/ Port.  The bitmap is printed at full
  144.             resolution on LaserWriters, and clipped on other printers (but
  145.             they still print).  It demonstrates how to use a high resolution
  146.             image as a PICT file, and how to print them out.
  147.             Written in MacApp Object Pascal code.
  148.             Compatibility rating = 1.  (The use of PrGeneral is slightly 
  149.                 out of the ordinary, although supported.)
  150. }
  151.  
  152. { Reasons for this version of reality (the strategy):
  153.     This program is intended to show a proper way of handling large offscreen buffers
  154.     and to demonstrate how to print high resolution images in the most compatible fashion.
  155.     It will calculate the Mandelbrot set at 300 dots per inch in an offscreen buffer,
  156.     scaling down the image to the screens 72 dpi so that you get the WYSIWYG of a
  157.     typical 8.5x11 page.  The documents can be saved and opened, and are stored as
  158.     PICT files.  They can be printed on any printer, although they will be clipped on
  159.     anything that has less than 300 dpi resolution (like the AppleTalk ImageWriter).
  160.  
  161.     The main idea behind this program is to allow you to create and fool around
  162.     with the Mandelbrot set, using this number cruncher wizzo computer we got
  163.     here.  This program is intended to be a real-life program,
  164.     done as well as possible given current constraints.  The program is supposed
  165.     to be something that somebody (who was silly perhaps) might try to sell.
  166.     The idea is to pretend that we are developers as well, testing the development
  167.     world as well as making a fun program.  Well, life's a bitch as a developer
  168.     trying to write a program of this form.  No intentional shortcuts were taken
  169.     in the program, but some things were left out due to time constraints.  Like
  170.     all real programs, some people will like it and some people will hate it.  I
  171.     hope you like it, but if you don't, send me some mail telling me why.
  172.  
  173.     The overall structure of the program is to have the MacApp Document object
  174.     handle all the data.  This includes an offscreen Port that contains
  175.     the actual fractal data.  The View object uses the Documents data to draw
  176.     into the window visible to the user.  The Document does all the work of
  177.     calculating new fractals during Idle times.  It also handles saving the
  178.     data to disk and reading it back.  The data files are PICT so as to be as
  179.     compatible as we can.  The program handles zooming in to
  180.     see closer views of the environment, using selection rectangles as you
  181.     would expect.  This whole block of comments at the beginning are intended
  182.     to describe some more macroscopic problems and structure.  In the code
  183.     itself you will find the tactical comments dealing with how a specific
  184.     operation is done.
  185.  
  186.     The fractal calculation is done by the CalcCity routine of the Document.  The
  187.     Application object gets the DoIdle call, and he calls each document to
  188.     have a pixel calculated in each document.  The calculation is done a pixel
  189.     at a time so that it can be done in the background with no visible effect
  190.     on the foreground app.  As each horizontal line of data is finished it is
  191.     updated to the screen.  The algorithm is very simple.
  192.  
  193.     Allocating and using an offscreen port are found in the Document
  194.     object, as the BuildOffWorld method.  The port offscreen is used as a drawing
  195.     environment once a pixel is calculated, as seen in DoIdle for the Document.
  196.     This allocation is more conventionally done in the DoMakeDocument method.
  197.     This would work here as well, since we have a fixed 8.5x11 page size that
  198.     we are using; but we wanted to keep this similar to the color versions of the
  199.     program where the page size is not fixed.
  200.  
  201.  
  202.     This program uses the Palette Manager in a simplistic fashion since it does
  203.     not use color.  In order to print in the current world, it is best to use only
  204.     black and white.  This is the Palette associated with each window when it
  205.     is opened.  The resource ID is the same as that for the window so the Palette
  206.     Manager will use that Palette.
  207.  
  208.     This version of the program currently uses a full page as the determining size of
  209.     the view and thus the fractal that is calculated.  This is OK, but a better approach
  210.     would be to allow the user to specify the limits of the Fractal they want.  This
  211.     involves adding another dialog and save defaults type of feature but is a better
  212.     way to solve the problem since it is not obvious what the best size would be.
  213.     A full page is easy to use, but whenever there is no obvious best answer
  214.     it is always better to let the user decide instead, that way they can't bitch.
  215.  
  216.     Another goal of course was to get this thing done.  A goal that tends to slide
  217.     away as more things are added to the program, so in true Macintosh style,
  218.     the 1.0 version of the program is somewhat limited, and may not be fully
  219.     debugged.  Some things specifically left out: printing the documents to
  220.     a LaserWriter with grey scales instead, using temporary documents to
  221.     make the program crashless (so you can start up where you left off, saving
  222.     the computation it took to get there), an option to make the pen size bigger
  223.     so you can do a low-res fractal to begin with.  These things are all admirable
  224.     features to add, but you have to finish version 1.0 sometime, so this was it.
  225.     These other things will be added if possible.
  226.  
  227.     Carefully watch the 881 flag with this MPW business.  There are a number of
  228.     ridiculous problems associated with its use.  In particular the $LOAD files
  229.     are dangerous to use with 881 and the combination of the two will often
  230.     end up compiling successfully into a program that is garbage and will
  231.     crash upon running it.  This, remind yourself, is a feature of the most
  232.     powerful development system around.  In order to build currently, the main
  233.     program MFracApp.p should be compiled with new $LOAD files and the 881
  234.     flag turned off.  All of the MacApp sources should be compiled with a new
  235.     $LOAD file and 881 turned off.  The last step should be to compile the UFracApp.p
  236.     with the 881 flag turned on, and with new $LOAD files as well.  Beware or
  237.     be prepared to spend a lot of time on something silly.  To solve this
  238.     problem the Make file used with FracApp has a specific compile rule for
  239.     the UFracApp file.  UFracApp also uses its own $LOAD file, that is different
  240.     from the MacApp and MFracApp ones.  The 881 flag is not specifically
  241.     required, but it makes code that uses the 881 directly instead of going
  242.     through SANE for a speed up of about 10 times.  Since we are speed freaks
  243.     here as well, the 881 option had to be used.  Given the problems, I would
  244.     probably skip the 881 option and do the time critical pieces in assembler.
  245.  
  246.     If you want to know how long a fractal took to calculate there is a time
  247.     stamp saved in the file header.  It is no longer drawn in the window since
  248.     it is not accurate when the program has run in the background or if there
  249.     are multiple documents open.  This could be added again if desired.
  250.  
  251.     Notably I am aware of the fact that this program does not really calculate
  252.     Fractals.  Actually it calculates and displays the Mandelbrot set which is
  253.     not self-similar so it cannot really be called a fractal.  It is distressing to
  254.     add to the confusion as to what fractals are, but it is too late.  For more
  255.     information on Fractals and the Mandelbrot set (no umlaut on the o), you
  256.     could see Mandelbrots book The Fractal Geometry of Nature, but it is
  257.     pretty mathematical and not all that helpful.  A better source is the
  258.     Peitgen-Richter book The Beauty of Fractals, which has a do it yourself
  259.     section in the back.
  260.  
  261.     The program is structured primarily around the document.  The document is
  262.     the object to create and maintain the offscreen port.  The document
  263.     converts the offscreen data into a PICT file when saved or restored.  The
  264.     document also does the calculation of the fractal, keeping the offscreen data
  265.     up to date as it goes along.  The view only handles taking the document data
  266.     and displaying it.
  267.  
  268.     For the zoom operation, there was no really great way to handle the new
  269.     document case based on another document.  This is a little strange to be
  270.     doing, and the structure of MacApp was such that we couldn't get to the
  271.     data desired at the right time.  The logical place to put it in at
  272.     DoMakeDocument was too early to have the port allocated and ready
  273.     to start more stuff.  The problem was resolved by using global variables
  274.     to transmit the information to the other piece of the program that
  275.     might need it.  Essentially DoInitialState decides if this is a brand new
  276.     base level document or a zoom in based on the state of the global variables.
  277.  
  278.     The MacApp memory management approach is used as well.  The big pieces
  279.     used by the Documents come out of permanent memory, helping to avoid
  280.     a crash from no memory.  When we allocate something that will be thrown
  281.     away immediately, like a spare color table or something, it of course
  282.     comes out of normal memory.  We did not do a full blown memory analysis
  283.     of the program since it is such a memory hog anyway.  The mem! resource
  284.     is set up in a form that is roughly close (a bit high) without trying to be
  285.     extra accurate.  When a document takes 400K of RAM to open it hardly
  286.     seems relevant to make sure the mem! is accurate to 2K.  Because of this
  287.     somewhat cavalier approach you may not be able to open a document in
  288.     a few cases where you really should be able to.  The mem! in use of 40K
  289.     is close with a +2K/-10K error on how big it should really be.
  290.  
  291.     The time stamp as the elapsedTime field in the FracHeader is not fully
  292.     accurate, so it is no longer displayed.  Currently it is set as an elapsed
  293.     time from when it was started to when it ended, which has a number
  294.     of problems.  This will be made more accurate in the future, probably
  295.     using a calculation that gives us a once through the loop calculation
  296.     in units of TimeDBRA, so we can be more machine independent.
  297.  
  298.     The QuickDraw BottleNecks are used to both read and write the actual
  299.     fractal data from/to the disk.  This is done since the data in the document
  300.     may be very large (500K) and if we just spool the data from the file
  301.     we don't actually have to use that extra hunk of memory.  We have to read
  302.     the data anyway, so we go ahead and just read it in as we play it back.
  303.     As it is played back it goes into the offscreen port, so we
  304.     have the data to display.  No memory hit for the document is a big win.
  305.     When writing the data, the same thing is true so we don't have to have
  306.     a huge handle to hold the picture data itself.  We also avoid the problem
  307.     of not having enough memory to create the picture in the first place,
  308.     making a document unsaveable.  That is particualarly annoying, and
  309.     is easy to avoid using the spooling approach.
  310.  
  311.     With thanks to Skippy Blair for the discussions of color QuickDraw and the
  312.     Palette Manager.  Thanks to Darin Adler for further discussion of the Palette
  313.     Manager and for good suggestions on making it more MacApp friendly.  Thanks
  314.     to Chris Derossi for providing the first 300 dpi fractal printing, and to ZZ
  315.     Zimmerman for the next generation code to print compatibly with PrGeneral.
  316.     }
  317.  
  318.  
  319.     { Global variables.  }
  320. VAR
  321.     gStaggerCount:     INTEGER;                { for staggering windows. }
  322.     gRealMin,
  323.     gRealMax,
  324.     gImagMin,
  325.     gImagMax:            Extended;                { used for zooming in operation. }
  326.  
  327.         { The next globals are used for the QuickDraw bottlenecks when reading or
  328.             writing a picture to disk.  These are needed, since the bottlenecks cannot
  329.             be owned procedures. }
  330.     gPictSize:            LongInt;                { number of bytes used for saving a PICT. }
  331.     gPictError:            OSErr;                    { do some error handling in bottleneck. }
  332.     gPictRefNum:        Integer;                { Need the refnum of the open file too. }
  333.     gPictHandle:        PicHandle;            { for reading/writing a picture. }
  334.  
  335.  
  336. { Set some compiler options that we desire for the main body of code only.  You
  337.     might wish to leave on the range checking, but I was not satisfied with having
  338.     to push and pop in the code, and was not pleased with the slowdown in performance.
  339.     These can be dangerous to turn off, especially the $H.  For those who use MPW
  340.     more than I, you probably want to make these settable from the command line,
  341.     or use the MacApp debug or no-debug compile time variables.  }
  342. {$PUSH}        { Save the compiler state before we change it. }
  343. {$D+}            { Debugging labels on for the code here. }
  344. {$R-}            { No range checking to make things faster. }
  345. {$OV-}            { No overflow checking either. }
  346. {$H-}            { No handle checking to avoid compiler complaints on WITHs.  Be careful. }
  347.  
  348.  
  349. {-------------------------------  Application  -------------------------------}
  350.  
  351. PROCEDURE TFracAppApplication.IFracAppApplication(itsMainFileType: OSType);
  352.  
  353. VAR        I:                        Integer;
  354.  
  355. BEGIN
  356.     gStaggerCount := 0;
  357.     IApplication(itsMainFileType);
  358.     fIdlePriority := 1;                        { say we need Idle time calls to TApp.DoIdle }
  359.     gRealMin := 0;                            { must be set to empty to start with. }
  360.     gRealMax := 0;                            { so we do normal document open. }
  361. END;        { TFracAppApplication.IFracAppApplication }
  362.  
  363.  
  364.     { OK, this is where a new document gets created.  This does the init for the
  365.         document object itself.  After it is done, the view and window can
  366.         be created, relying upon the data in the document. }
  367. FUNCTION  TFracAppApplication.DoMakeDocument(itsCmdNumber: CmdNumber):
  368.         TDocument; OVERRIDE;
  369.  
  370. VAR        aFracAppDocument:    TFracAppDocument;
  371.  
  372. BEGIN
  373.     { Allocate and initialize the document}
  374.     New(aFracAppDocument);
  375.     FailNil(aFracAppDocument);
  376.  
  377.     { Now initialize the document fields, and set up the global state of the fractal
  378.         to a default set of the starting fractal. }
  379.     aFracAppDocument.IFracAppDocument;
  380.  
  381.     { We successfully created a document so we can return the document object for
  382.         use by the application. }
  383.     DoMakeDocument := aFracAppDocument;
  384.  
  385. END;    { TFracAppApplication.DoMakeDocument }
  386.  
  387.  
  388.     { Performs Idle time processing for the application.  This will do the
  389.         fractal calculation during the idle times.  It will allow each open
  390.         document a chance to calculate.  The CalcCity is a method owned by
  391.         each document that will get a call from the ForAllDocumentsDo.
  392.         The documents don't use the DoIdle routine since we want each
  393.         open document to get time, not just the one in the target chain. }
  394. PROCEDURE  TFracAppApplication.DoIdle (phase: IdlePhase); OVERRIDE;
  395.  
  396.         { Give each document some CPU time. }
  397.     PROCEDURE DoFractalCalc(aDocument: TFracAppDocument);
  398.  
  399.     BEGIN
  400.         aDocument.CalcCity;                        { give the document its time to calc. }
  401.     END;
  402.  
  403. BEGIN
  404.         { Send the message to each open document to calculate the next pixel. }
  405.     IF phase = IdleContinue  THEN  ForAllDocumentsDo (DoFractalCalc);
  406. END;        { TFracAppApplication.DoIdle }
  407.  
  408.  
  409.  
  410. {-------------------------------  Document  -------------------------------}
  411.  
  412.     { An auxiliary method to set up the step constants for the fractal calculation.
  413.         It is external since we need to set up the constants when we create a new
  414.         fractal as a zoom in.   Sets up the width/height of fractal, the delta in each
  415.         axis as a real number, and ensures that the starting min/max values for
  416.         the figure are set to supply a 1:1 aspect ratio.  The step constants are
  417.         zeroed to start the fractal anew.  Allocates no memory. }
  418. PROCEDURE  TFracAppDocument.SetUpConstants;
  419.  
  420. BEGIN
  421.     WITH fFracHeader  DO  BEGIN
  422.         { Set up the iterations by calculating up the step constants, and the
  423.             edges of the view area in pixels. }
  424.  
  425.         plotWidth := (calcRect.Right - calcRect.Left);
  426.         plotHeight := (calcRect.Bottom - calcRect.Top);
  427.         deltaP := (realMax-realMin)/(plotHeight-1);
  428.         deltaQ := (imagMax-imagMin)/(plotWidth-1);
  429.  
  430.         { Force aspect ratio 1:1, making delta smallest of two.  This effectively grows
  431.             one side or the other out, like rMax/iMax becoming bigger number. }
  432.         IF deltaP > deltaQ  THEN  BEGIN
  433.             deltaQ := deltaP;
  434.             imagMax := deltaQ * (plotWidth-1) + imagMin;     { new maximum for q }
  435.         END   { grow the q side }
  436.         ELSE  BEGIN
  437.             deltaP := deltaQ;
  438.             realMax := deltaP * (plotHeight-1) + realMin;         { new maximum for p }
  439.         END;  { grow the p side }
  440.  
  441.         { Now start the counters at zero, as the edge of the area to calc. }
  442.         curCol := 0;  curRow := 0;
  443.  
  444.         { And the elapsed time is zero of course, since we are just starting. }
  445.         elapsedTime := 0;
  446.     END;        { WITH fractalDocument }
  447. END;        { SetUpConstants }
  448.  
  449.  
  450.     { Utility method to build the offscreen Port that is used for
  451.         the document data.  This happy fellow will allocate huge old hunks of Ram for
  452.         the document.  This is done as a utility routine since we don't know in advance
  453.         how big the port will be, and we want to make it as big as it was when the
  454.         document was saved, reading it from the header.  If we are making a new
  455.         document, the DoInitialState will call with the page rectangle.
  456.         For this version we could have moved this back into the DoMakeDocuments
  457.         where it belongs, but that would make each example even more different
  458.         which is extra confusing. }
  459.  
  460. PROCEDURE  TFracAppDocument.BuildOffWorld (sizeOfDoc: Rect);
  461.  
  462. VAR        oldPerm:             Boolean;
  463.             dummy:                Boolean;
  464.             docW, docH:        LongInt;
  465.             fi:                     FailInfo;
  466.             currPort:            GrafPtr;
  467.             Erry:                    OSErr;
  468.  
  469.         { This is the error handler for when we get errors while making a new document,
  470.             typically like running out of memory.  Since the Free method for the document
  471.             will get called we don't have to chuck the things that normally get killed.
  472.             Just set allocation back to normal (for the error message itself), the drawing
  473.             environment back to normal and return.  }
  474.     PROCEDURE DeathDocument (error: OSErr; message: LONGINT);
  475.  
  476.     BEGIN
  477.         oldPerm := PermAllocation (oldPerm);    { Set memory back to previous. }
  478.  
  479.         SetPort (currPort);
  480.     END;
  481.  
  482. BEGIN
  483.     GetPort(currPort);
  484.  
  485.     CatchFailures(fi, DeathDocument);            { any failures, must be cleaned up. }
  486.  
  487.         { The memory used creating the view must be out of permanent memory, it is too
  488.             big.  Any failure to get it from permanent memory will invoke the error handler. }
  489.     oldPerm := PermAllocation (TRUE);
  490.  
  491.         { Let's set up the size of the rectangle we are using for the document. }
  492.     docW := sizeOfDoc.right - sizeOfDoc.left;
  493.     docH := sizeOfDoc.bottom - sizeOfDoc.top;
  494.  
  495.         { Now try to set up the offscreen bitMap (color).  If we fail we have to split,
  496.             and we might since we may not have 800K or more (basically a full screen
  497.             worth, which is unlikely to be less than 800K) for the pixMap.  Each document
  498.             on screen will have a full port for it.  Allocate a full screen size buffer in
  499.             1 bit depth.  Also make it into a standard port so we can draw into it normally
  500.             and use it as a source for CopyBits.  The buffer is (width DIV 8) giving bytes/row;
  501.             then that times height to give total bytes required for the buffer }
  502.     fBigBuff := NewPtr (docW * docH DIV 8);
  503.     FailMemError;                                                { couldn't get it we die. }
  504.  
  505.         { The port is simply an interface for drawing.
  506.             Allocate a port record on the heap as a pointer.  (permanent memory useage).  We
  507.             allow the port to come out of temporary memory since it will blow up (most
  508.             unfriendly) if it cannot do it.  After it lives, though we want to check if we
  509.             have no more reserve, and if so we must bag this document. }
  510.     fDrawingPort := GrafPtr( NewPtr (SizeOf (GrafPort)) );    { address of Graf Port record. }
  511.     FailNil (fDrawingPort);                                { didn’t get it, means we die. }
  512.     
  513.         { Now the world is created, put memory allocation back to temporary, so that the
  514.             QD pieces can come out of temp memory as well.  No more permanent blocks are
  515.             allocated by us, except for the port, which cannot fail or we die.  }
  516.     dummy := PermAllocation (FALSE);
  517.  
  518.     OpenPort (fDrawingPort);                                { make a new port offscreen. }
  519.     FailNoReserve;                                                { Make reserve, die if we can't }
  520.  
  521.         { QuickDraw is most obnoxious about making a port that is bigger than the screen,
  522.             so we need to modify the visRgn to make it as big as our full page document.  It is
  523.             OK to change this ports visRgn since we own it offscreen. }
  524.     RectRgn(fDrawingPort^.visRgn, sizeOfDoc);
  525.  
  526.         { Go whap on the other pieces of the port record to set it up to be offscreen. }
  527.     fDrawingPort^.portBits.baseAddr := fBigBuff;
  528.     fDrawingPort^.portBits.bounds := sizeOfDoc;
  529.     fDrawingPort^.portBits.rowBytes := sizeOfDoc.right DIV 8;
  530.     fDrawingPort^.portRect := sizeOfDoc;
  531.  
  532.         { Clear the error handler chain, we don't make any more dangerous requests. }
  533.     Success (fi);
  534.  
  535.  
  536.         { Set the memory allocation to what we started with. }
  537.     oldPerm := PermAllocation (oldPerm);
  538.  
  539.         { Now we have the offscreen PixMap, we need to initialize it to white. }
  540.     SetPort (fDrawingPort);
  541.     EraseRect (sizeOfDoc);                { clear the bits. }
  542.  
  543.         { We are done drawing and stuff for now, so set the port back to where it was. }
  544.     SetPort (currPort);
  545. END;    { BuildOffWorld }
  546.  
  547.  
  548.     { Init for the FracAppDocument itself.  This sets up the Document object. }
  549. PROCEDURE      TFracAppDocument.IFracAppDocument;
  550.  
  551. VAR        dummyTime:        LongInt;        { for picky compiler }
  552.  
  553. BEGIN
  554.     { Set up failure mechanism in case IDocument fails}
  555.     IDocument(kFileType, kSignature, kUsesDataFork,
  556.             NOT kUsesRsrcFork, NOT kDataOpen, NOT kRsrcOpen);
  557.  
  558.     { Set the time in our starting time variable in case we are still calculating.
  559.         Temp var is to make the picky compiler not get worried about the var
  560.         parameter.  This routine can't move memory anyway, but it won't allow
  561.         this use. }
  562.     GetDateTime (dummyTime);
  563.     fStartTime := dummyTime;
  564.  
  565.     fBigBuff := NIL;
  566.     fDrawingPort := NIL;                                    { set up in case we fail in here. }
  567. END;    { TFracAppDocument.IFracAppDocument }
  568.  
  569.  
  570.     { Does the work for a New operation, where we start with a new fractal
  571.         that doesn't have any stored data.  This is to set up the view with no
  572.         data and set up the fractal coordinates to the default.  It will use the size
  573.         of the  main screen to make a new document, and create the offscreen
  574.         world to match.  If the global variable of gRealMin and gRealMax are both
  575.         nonzero, then we want to use the global state being passed us by the
  576.         New Fractal handler.  This is for the zoom in. }
  577. PROCEDURE TFracAppDocument.DoInitialState;  OVERRIDE;
  578.  
  579. BEGIN
  580.     WITH  fFracHeader  DO BEGIN
  581.             { Start by filling in the fields that never change. }
  582.         fType := kSignature;            { creator of these documents. }
  583.         hdrId := INTEGER ('FA');        { ID of the file, different from other PICT files. }
  584.         version := 1;                        { version 1 files. 0 was old MandibleJug docs. }
  585.  
  586.         done := FALSE;                    { not done, starting brand new document. }
  587.  
  588.         { We start from scratch.  This is the standard set of coordinates to start
  589.             the default Mandelbrot set.
  590.             Set up the coordinates to do, saving state in header vars. }
  591.         realMin := -2.5;  realMax := 1.5;
  592.         imagMin := -1.5;  imagMax := 1.5;
  593.  
  594.             { If we are supposed to do a zoom in, use those numbers instead. }
  595.         IF (gRealMin <> 0) AND (gRealMax <> 0)  THEN BEGIN
  596.             realMin := gRealMin;        realMax := gRealMax;
  597.             imagMin := gImagMin;    imagMax := gImagMax;
  598.         END;
  599.  
  600.             { Set the fractal rectangle to be the full page size at 300 dpi. }
  601.         SetRect(calcRect, 0, 0, kRectRight, kRectBottom);
  602.  
  603.     END;        { With FracHeader }
  604.  
  605.         { Clear the state of the globals so any new documents will not be zoom in types. }
  606.     gRealMin := 0;        gRealMax := 0;
  607.  
  608.         { Build the initial state of the document offscreen gDevice & port }
  609.     BuildOffWorld (fFracHeader.calcRect);
  610.  
  611.         { Set up the rest of the constants that are used in the fractal, including
  612.             the deltas in each axis and the step constants for stepping through
  613.             each point in the fractal plane. }
  614.     SetUpConstants;
  615. END;        { TFracAppDocument.DoInitialState }
  616.  
  617.  
  618. PROCEDURE TFracAppDocument.DoMakeViews(forPrinting: BOOLEAN); OVERRIDE;
  619.  
  620. VAR        aFracAppView:    TFracAppView;
  621.             fullRect:                Rect;
  622.             aFracAppHandler: TFracAppPrintHandler;
  623.  
  624. BEGIN
  625.         { Create a new view (failing if we can't), get a rectangle with
  626.             the appropriate extent, and initialize the view. }
  627.     New(aFracAppView);
  628.     FailNil(aFracAppView);
  629.  
  630.         { Initialize the view for use as a drawing environment.  The view rectangle
  631.             is one fourth the size of the full page, since the screen is roughly one
  632.             fourth the printer resolution.  This will produce the scaling down from
  633.             300 dpi to screen res during update events. }
  634.     SetRect (fullRect, 0, 0, kRectRight DIV 4, kRectBottom DIV 4);
  635.     aFracAppView.IFracAppView (SELF, fullRect);
  636.  
  637.         {save a reference to the view in a TFracAppDocument field, for use
  638.             by DoMakeWindows}
  639.     fFracAppView := aFracAppView;
  640.  
  641.         { Initialize our special print handler object so we can print.  This print
  642.             handler will be the target of the print operation. }
  643.     New(aFracAppHandler);
  644.     FailNIL(aFracAppHandler);
  645.     aFracAppHandler.IFracAppPrintHandler(aFracAppView);
  646.  
  647. END;    { TFracAppDocument.DoMakeViews }
  648.  
  649.  
  650. PROCEDURE TFracAppDocument.DoMakeWindows; OVERRIDE;
  651.  
  652. VAR        aWindow:    TWindow;
  653.  
  654. BEGIN
  655.         { Gets window definition from resource file; the window is to have both horizontal
  656.             and vertical scrollbars, and is to have my 'fFracAppView' installed in it;
  657.             NewSimpleWindow will exit via the failure mechanism if allocation fails.
  658.             There is a palette associated with this window by Resource Id, so it will
  659.             automatically get used when the window is created. }
  660.     aWindow := NewSimpleWindow(kFracAppWindowID, NOT kDialogWindow,
  661.                     kWantHScrollBar, kWantVScrollBar, fFracAppView);
  662.  
  663.     SimpleStagger(aWindow, kStaggerAmount, kStaggerAmount, gStaggerCount);
  664. END;    { TFracAppDocument.DoMakeWindows }
  665.  
  666.  
  667.     { This routine will size the current image as it goes to the disk.  It won't actually
  668.         save any data or anything, but will merely watch the bytes go by keeping track
  669.         of how many go by.  The size is used by DoNeedDiskSpace. }
  670. PROCEDURE  PictSizer (dPointer: Ptr; nextHunk: Integer);
  671.  
  672. BEGIN
  673.     gPictSize := gPictSize + nextHunk;
  674. END;
  675.  
  676.  
  677.     { Routine to find out how much disk space will be required to save the data.
  678.         This does not call the Inherited DoNeedDiskSpace since we don't support
  679.         printing info here.  The routine will replace the PutPicProc of the port
  680.         with our PictSizer routine.  When the picture is created here, no bytes
  681.         will actually be allocated or saved, we will just watch it go by and
  682.         save off the size in the global variable.  That value is returned
  683.         as the expected document size. }
  684. PROCEDURE TFracAppDocument.DoNeedDiskSpace(VAR dataForkBytes,
  685.             rsrcForkBytes: LONGINT);   OVERRIDE;
  686.  
  687. VAR        picPort:            GrafPtr;
  688.             currPort:            GrafPtr;
  689.             newGrafs:            QDProcs;
  690.             oldProcs:            QDProcsPtr;
  691.  
  692. BEGIN
  693.     { Create a picture Item itself, by opening the picture and doing the CopyBits
  694.         operation to the same port.  That picture will then be packed using the
  695.         normal packing operation of the Mac.  That block is then the data to be
  696.         written to the file. }
  697.  
  698.     GetPort (currPort);
  699.  
  700.     picPort := fDrawingPort;                        { the pointer to our port. }
  701.     SetPort (picPort);                                            { set there to do pict saving. }
  702.  
  703.         { Save the pointer to the current CGrafProcs }
  704.     oldProcs := thePort^.grafProcs;
  705.  
  706.         { Set our GrafProc record up to have the standard pieces. }
  707.     SetStdProcs(newGrafs);
  708.  
  709.         { Change the port to use those GrafProcs instead. }
  710.     thePort^.grafProcs := @newGrafs;
  711.  
  712.         { We are in our offscreen port.  Change the GrafProc pointer for picture saving. }
  713.     newGrafs.putPicProc := @PictSizer;
  714.  
  715.         { Init the size of the pict we are going to save.  Start with picture header. }
  716.     gPictSize := SIZEOF (Picture);
  717.  
  718.         { The current gDevice is our offscreen device.  Now go ahead and open the picture
  719.             and build it in RAM.  We would have done this by slices before, but the newer
  720.             systems have a patch for playing back pictures that minimize the RAM hit, so
  721.             we don't have to worry about the full screen CopyBits here. }
  722.     WITH  picPort^  DO BEGIN
  723.         gPictHandle := OpenPicture (portRect);
  724.  
  725.                 { copy all of the image to itself, in an open picture it saves the bits. }
  726.             CopyBits (portBits, portBits, portRect, portRect, srcCopy, NIL);
  727.  
  728.         ClosePicture;                        { the picture is created, and packed. }
  729.     END;        { with picPort^ }
  730.  
  731.  
  732.         { Done saving the size of the picture itself.  Now set the GrafProcs back to normal. }
  733.     thePort^.grafProcs := oldProcs;
  734.  
  735.         { Dispose the pict handle, we didn't actually make anything there. }
  736.     KillPicture (gPictHandle);
  737.     gPictHandle := NIL;
  738.  
  739.         { Set the drawing device back where it belongs, in case of error, we get right device. }
  740.     SetPort (currPort);
  741.  
  742.         { The picture has been sized.  Now add that in to the total size the file will use on
  743.             disk, include the header for the file, plus the number of bytes in actual PICT. }
  744.     dataForkBytes := dataForkBytes + gPictSize + kPICTHeaderSize;
  745. END;        { DoNeedDiskSpace }
  746.  
  747.  
  748.     { This routine will save the current image as it is created.  As the data requests
  749.         go by that data will be written to the file.  The data is being created by the
  750.         OpenPicture/CopyBits in DoWrite, this is the bottleneck for that operation.
  751.         Any errors found while doing this will make us skip any further requests
  752.         to write data to the disk.  No memory is allocated.   Communication with
  753.         DoWrite is done through globals, since bottlenecks must be at the main
  754.         level.  The bottleneck must also keep track of how many bytes are written,
  755.         so that the header on the picture can be fixed up to be correct.  This must
  756.         be done to avoid creating bogus pictures.  The picSize field of the handle
  757.         must be updated continuously so that when the picture is done, the ClosePicture
  758.         can create a valid picture.  The check for the NIL handle is to handle the
  759.         problem of when the OpenPicture is called.  The proc gets called before
  760.         the handle is valid.  Be very careful of these bottleneck things, it is
  761.         easy to run into problems that are very hard to figure out.  QuickDraw
  762.         has no facilities to give you info when things go wrong so it makes it
  763.         a bit tougher. }
  764. PROCEDURE  PictWriter (dPointer: Ptr; nextHunk: Integer);
  765.  
  766. VAR    longHunk:    LongInt;
  767.  
  768. BEGIN
  769.     IF gPictError = noErr THEN  BEGIN
  770.         longHunk := nextHunk;
  771.         gPictError := FSWrite(gPictRefNum, longHunk, dPointer);
  772.         gPictSize := gPictSize + longHunk;
  773.         IF gPictHandle <> NIL THEN  gPictHandle^^.picSize := LoWord (gPictSize);
  774.     END;
  775. END;
  776.  
  777.  
  778.     { Write the data calculated into the document to the file.  This will make it a real
  779.         PICT file.  It writes the header first, then the PICT data.  This is so that it
  780.         will still be a normal PICT file and can be used by other programs.
  781.         The file will be saved using QuickDraw Bottlenecks for the PutPicProc.
  782.         As the data requests go by, they will be written to the file, using the
  783.         PictWriter routine. }
  784. PROCEDURE TFracAppDocument.DoWrite(aRefNum: INTEGER; makingCopy: BOOLEAN);
  785.             OVERRIDE;
  786.  
  787. VAR        recSize:            LongInt;
  788.             fi:                     FailInfo;
  789.             picPort:            GrafPtr;
  790.             currPort:            GrafPtr;
  791.             newGrafs:            QDProcs;
  792.             oldProcs:            QDProcsPtr;
  793.  
  794.     PROCEDURE DeathWrite (error: OSErr; message: LONGINT);
  795.     BEGIN
  796.         IF gPictHandle <> NIL THEN      KillPicture (gPictHandle);
  797.         gPictHandle := NIL;
  798.  
  799.         thePort^.grafProcs := oldProcs;
  800.         SetPort (currPort);
  801.     END;
  802.  
  803. BEGIN
  804.         { We have legit data in our document, set the mark in the file to be at the front. }
  805.     FailOSErr ( SetFPos (aRefNum, fsFromStart, 0) );
  806.  
  807.         { Write the FracHeader to the file, it includes the pertinent details about
  808.             the fractal including the global state for it. }
  809.     recSize := SIZEOF (FracRecord);                           { our header on fractal files. }
  810.     FailOSErr ( FSWrite (aRefNum, recSize, @fFracHeader) );
  811.  
  812.         { Now we need to write the picture data itself out to the file, after we set the
  813.             mark to be after the entire header.  Make sure the file is that big before we do it.
  814.             Included in this set is the header of the picture itself, the 10 bytes that
  815.             include the rectangle.  Those bytes will be updated after the picture is
  816.             written. }
  817.     FailOSErr ( SetEOF (aRefNum, kPICTHeaderSize+SIZEOF (Picture) ) );
  818.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize+SIZEOF (Picture) ) );
  819.  
  820.         { The file is all set up to go.  We now want to replace the QuickDraw bottleneck
  821.             and create the actual Picture data. }
  822.     GetPort (currPort);
  823.  
  824.         { If the write of the picture header fails, we want to dispose the handle allocated. }
  825.     CatchFailures(fi, DeathWrite);
  826.  
  827.         { Move over to the offscreen port/device. }
  828.     picPort := fDrawingPort;                    { the pointer to our port. }
  829.     SetPort (picPort);                                            { set there to do pict saving. }
  830.  
  831.         { Save the pointer to the current CGrafProcs }
  832.     oldProcs := thePort^.grafProcs;
  833.  
  834.         { Set our GrafProc record up to have the standard pieces. }
  835.     SetStdProcs(newGrafs);
  836.  
  837.         { Change the port to use those GrafProcs instead. }
  838.     thePort^.grafProcs := @newGrafs;
  839.  
  840.         { We are in our offscreen port.  Change the GrafProc pointer for picture saving. }
  841.     newGrafs.putPicProc := @PictWriter;
  842.  
  843.         { Tell PictWriter what file to write to, and start the pic size including the
  844.             picture header.  Start all the pieces off the right way. }
  845.     gPictRefNum := aRefNum;
  846.     gPictSize := SIZEOF(Picture);
  847.     gPictError := noErr;
  848.     gPictHandle := NIL;
  849.  
  850.         { Actually open the picture and do the CopyBits in order to process the picture.
  851.             The data will be written by PictWriter as it is called by QuickDraw. }
  852.     WITH  picPort^  DO BEGIN
  853.         gPictHandle := OpenPicture (portRect);
  854.             ClipRect(portRect);            { Make it a happier picture. }
  855.  
  856.                 { copy all of the image to itself, in an open picture it saves the bits. }
  857.             CopyBits (portBits, portBits, portRect, portRect, srcCopy, NIL);
  858.  
  859.         ClosePicture;                        { the picture is created, and packed. }
  860.     END;        { with picPort^ }
  861.  
  862.         { Now check for errors during the write operation.  The gPictError field will be
  863.             nonzero if we failed during the operation. }
  864.     FailOSErr (gPictError);
  865.  
  866.         { Move back to front of file and write the valid picture info to file. }
  867.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize) );
  868.     recSize := SIZEOF(Picture);
  869.     FailOSErr (FSWrite(aRefNum, recSize, Ptr(gPictHandle^)));
  870.  
  871.         { Done saving the data of the picture itself.  Now set the GrafProcs back to normal. }
  872.     thePort^.grafProcs := oldProcs;
  873.  
  874.         { Dispose the pict handle, we didn't actually make anything there. }
  875.     KillPicture (gPictHandle);
  876.     gPictHandle := NIL;
  877.  
  878.         { Set the drawing device back where it belongs, in case of error, we get right device. }
  879.     SetPort (currPort);
  880.  
  881.         { If we lived through it, clear error handler. }
  882.     Success (fi);
  883. END;            { TFracAppDocument.DoWrite }
  884.  
  885.  
  886.     { The bottleneck routine to read the picture from the disk.  This will read the
  887.         data required, and pass it along to the unpacker.  This makes it possible to
  888.         avoid using any RAM for the actual reading part, as it is being played back
  889.         into the offscreen device.  Error handling is somewhat tricky, since we
  890.         need to force the picture to finish, and there isn't a really good way to
  891.         do this.  The desired attempt here is to pass back a picture is finished
  892.         opcode ($00FF) so we can get back to our code to handle the error.  This is
  893.         better than no error recovery, but is not guaranteed to work. }
  894. PROCEDURE  PictReader (dPointer: Ptr; nextHunk: Integer);
  895.  
  896. VAR    longHunk:    LongInt;
  897.         I:                Integer;
  898.  
  899. BEGIN
  900.     IF gPictError = noErr THEN  BEGIN
  901.         longHunk := nextHunk;
  902.         gPictError := FSRead(gPictRefNum, longHunk, dPointer);
  903.     END
  904.     ELSE            { handle the error situation by passing back $00FF as the data.? }
  905.         FOR I := 1 to nextHunk  DO BEGIN
  906.             IF ODD (I) THEN  dPointer^ := $FF
  907.             ELSE  dPointer^ := $00;
  908.             dPointer := PTR (ORD4(dPointer) + 1);
  909.         END;
  910. END;
  911.  
  912.  
  913.     { Routine to read the data from the data fork of the file into our document so it
  914.         can be displayed.  The quickdraw bottleneck will be replaced with the
  915.         PictReader routine, making it read the data from the disk as the picture
  916.         requests more data.  This obviates the need for an extra handle that is
  917.         used to play back the picture.  This is done since that extra handle can
  918.         be on the order of 400K, memory we may not have available. }
  919. PROCEDURE TFracAppDocument.DoRead(aRefNum: INTEGER; rsrcExists,
  920.                                                   forPrinting: BOOLEAN);  OVERRIDE;
  921.  
  922. VAR        recSize:            LongInt;
  923.             fi:                     FailInfo;
  924.             currPort:            GrafPtr;
  925.             newGrafs:            QDProcs;
  926.             oldProcs:            QDProcsPtr;
  927.  
  928.     PROCEDURE DeathRead (error: OSErr; message: LONGINT);
  929.     BEGIN
  930.         IF gPictHandle <> NIL THEN  KillPicture (gPictHandle);
  931.         gPictHandle := NIL;
  932.     END;
  933.  
  934. BEGIN
  935.         { The file is open already, we just have to read the data out of it.  The first thing
  936.             to read is the header we use to describe a fractal.  If we get an error
  937.             here we need to split since we should always have at least a header.  The fractal
  938.             header is the global state for the document.  We just read it into the record
  939.             and use it from there. }
  940.     FailOSErr ( SetFPos (aRefNum, fsFromStart, 0) );    { starts at first byte of file. }
  941.     recSize := SIZEOF (FracRecord);                                   { size of header on fractal files. }
  942.     FailOSErr ( FSRead (aRefNum, recSize, @fFracHeader) );
  943.  
  944.         { We have the header for the PICT file.  Now we need to be sure that it is a fractal
  945.             document, and not something we can't use.  Check the header to be sure, and if
  946.             not right, error out with a good alert message (using a standard MacApp errcode). }
  947.     IF fFracHeader.fType <> kSignature  THEN  FailOSErr (errNotMyType);
  948.  
  949.         { We have the data from the header, go ahead and set up an offscreen world for this
  950.             document, using the header rectangle. }
  951.     BuildOffWorld (fFracHeader.calcRect);
  952.  
  953.         { Make sure the file position is right at the start of the picture in the file. }
  954.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize) );
  955.  
  956.         { Allocate a small handle that will be used as the Pict handle for drawing from
  957.             the disk.  This is just the picture header. }
  958.     gPictHandle := PicHandle (NewHandle (SIZEOF(Picture)));
  959.     FailNil (gPictHandle);
  960.  
  961.         { If the read of the picture header fails, we want to dispose the handle allocated. }
  962.     CatchFailures(fi, DeathRead);
  963.  
  964.         { Tell PictReader what file to read from. }
  965.     gPictRefNum := aRefNum;
  966.     gPictError := noErr;
  967.  
  968.         { Now fill in the picture header itself, using the data from the disk. }
  969.     recSize := SIZEOF(Picture);
  970.     gPictError := FSRead(aRefNum, recSize, Ptr (gPictHandle^));
  971.     FailOSErr (gPictError);
  972.  
  973.         { That is the only call we can’t recover from immediately, the rest of the
  974.             routine is not easy to recover from, so we won’t go through DeathRead. }
  975.     Success (fi);
  976.     
  977.         { The file position is right at the beginning of the picture data, so we can just
  978.             install the bottleneck and call DrawPicture to fill our offscreen gDevice
  979.             with the data that was saved.  Set to that port and gDevice for playback. }
  980.     GetPort (currPort);
  981.  
  982.     SetPort (fDrawingPort);
  983.  
  984.         { Save the pointer to the current CGrafProcs }
  985.     oldProcs := thePort^.grafProcs;
  986.  
  987.         { Set our GrafProc record up to have the standard pieces. }
  988.     SetStdProcs(newGrafs);
  989.  
  990.         { Change the port to use those GrafProcs instead. }
  991.     thePort^.grafProcs := @newGrafs;
  992.  
  993.         { We are in our offscreen port.  Change the GrafProc pointer for picture reading. }
  994.     newGrafs.getPicProc := @PictReader;
  995.  
  996.         { Now we have the buffer and the offscreen port.  We can draw the picture that
  997.             will be read out of the file into this port in order to init the port for later use in
  998.             updating the window.  We are already set to draw in the offscreen port.  Do the
  999.             DrawPicture to have PictReader read the data out of the file while it is being
  1000.             played into the offscreen Port. }
  1001.     DrawPicture(gPictHandle, gPictHandle^^.picFrame);
  1002.  
  1003.         { Done reading the data of the picture itself.  Now set the GrafProcs back to normal. }
  1004.     thePort^.grafProcs := oldProcs;
  1005.  
  1006.         { Bag the handle we made for playing back the picture. }
  1007.     KillPicture (gPictHandle);
  1008.     gPictHandle := NIL;
  1009.  
  1010.         { Set back to the normal drawing environment. }
  1011.     SetPort (currPort);
  1012.  
  1013.         { If we had an error while reading the data, we must error out. }
  1014.     FailOSErr (gPictError);
  1015. END;        { TFracAppDocument.DoRead }
  1016.  
  1017.  
  1018.     { This is typically used in a Revert case which is not really meaningful here, but
  1019.         the structure is the same so we use it anyway.  Frees the data associated with
  1020.         a document, that is strictly program data, not MacApp data. }
  1021. PROCEDURE TFracAppDocument.FreeData; OVERRIDE;
  1022.  
  1023. BEGIN
  1024.         { Kill the bits for the offscreen bitMap if they were allocated. }
  1025.     IF fBigBuff <> NIL  THEN  DisposPtr (fBigBuff);
  1026.         { Close the port: remove from portList, kill visRgn and clipRgn, kill the penPixPat
  1027.             and fill PixPat and back PixPat, kill PixMap handle, kill grafVars handle. }
  1028.     IF fDrawingPort <> NIL  THEN  BEGIN
  1029.         ClosePort (fDrawingPort);
  1030.         DisposPtr (Ptr (fDrawingPort) );
  1031.     END;
  1032. END;    { TFracAppDocument.FreeData }
  1033.  
  1034.  
  1035.     { Free method for the documents themselves.  We need to override so that we
  1036.         can throw away the data object that was read in from the disk if it exists.
  1037.         Also chuck the buffer and port used for the document data. }
  1038. PROCEDURE TFracAppDocument.Free; OVERRIDE;
  1039.  
  1040. BEGIN
  1041.     FreeData;
  1042.  
  1043.     INHERITED Free;
  1044. END;    { TFracAppDocument.Free }
  1045.  
  1046.  
  1047.     { The procedure to do the idle time processing in the document.  This will do the
  1048.         entire fractal calculation so as to be able to do it in the background.  It
  1049.         does it one pixel at a time to avoid any hit on performance for the
  1050.         foreground application.  This is called in response to the DoIdle for the
  1051.         application.  The fIdlePriority is not set for this method, so it won't get
  1052.         time except when the application calls specifically.  It is done this way
  1053.         since otherwise the target chain would need to have each document in
  1054.         the list, which is not desireable for other event handling.  Notably the
  1055.         time keeper in here is not too accurate.  Each pixel takes less than a
  1056.         tick to calculate, making it a bit tougher.  A way to make it more
  1057.         accurate would be to figure out the maximum time for a full black
  1058.         document, and divide by the number of pixels in the screen and the
  1059.         number of loops.  That number (in microseconds) could be added each
  1060.         time through the calculation loop to give a more accurate timestamp.
  1061.         This would be wrong if the clock changes, so perhaps it should use the
  1062.         low memory TimeDBRA value as units instead.
  1063.         This is left as an exercise for the reader. }
  1064. PROCEDURE  TFracAppDocument.CalcCity;
  1065.  
  1066. CONST    M                = 100;                    { this decides what 'infinity' is.  If value less than this, loop. }
  1067.             K                = 195;                    { number of times to iterate while waiting for infinity. }
  1068.  
  1069. VAR        currTime:            LongInt;        { temp var for time check. }
  1070.  
  1071.             x,y,x1,y1:            Extended;        { for interim values of current point. }
  1072.             Po,Qo:                Extended;
  1073.  
  1074.             kol:                    Integer;        { color we are currently on. }
  1075.             r:                        Extended;        { 'distance' from root. }
  1076.             currPort:            GrafPtr;
  1077.             drawRect:            Rect;            { for updating the screen as we calculate. }
  1078.  
  1079. BEGIN
  1080.         { Calculate the fractal as we go.  Do next pixel here, based on the state saved
  1081.             in the document object.  When done, the variables are updated to go to the
  1082.             next location to do.  It sets the pixel in the offscreen port to be whatever
  1083.             we calculate it to be.  The buffer will be copied to the screen at update time.
  1084.             The global state is saved in the FracHeader record in the document object.
  1085.             That state is saved across the use of a document, so it will always be right. }
  1086.  
  1087.         { If we are done, or not started, we can split. }
  1088.     IF  fFracHeader.done THEN  Exit(CalcCity);
  1089.  
  1090.     GetPort (currPort);
  1091.     SetPort (fDrawingPort);        { draw in offscreen guy. }
  1092.  
  1093.     { Now do the calculation to determine the color of the pixel at the
  1094.         current location.  Uses the header saved state. }
  1095.  
  1096.     With  fFracHeader  DO BEGIN
  1097. (*        x := realMin + curCol * deltaP;                    { Use these for a Julia set calculation. }
  1098.         y := imagMin + curRow * deltaQ;
  1099.         kol := 0;
  1100.         Po := -0.39054;
  1101.         Qo := 0.58679;
  1102. *)
  1103.         Po := realMin + curRow * deltaP;                { next starting point  }
  1104.         Qo := imagMin + curCol * deltaQ;
  1105.         kol := 0;
  1106.         x := 0;                                                        { Mandel set starts with 0 always. }
  1107.         y := 0;                                                        { For Julia set you start with previous number. }
  1108.     END;        { With }
  1109.  
  1110.     REPEAT
  1111.         { the following is for y = X^2 + C for imaginary numbers.
  1112.         pt1 = x + yi,  C = Po + Qoi,  in pt2 := pt1^2 + C  }
  1113.  
  1114.         x1 := x*x - y*y + Po;
  1115.         y1 :=2*x*y + Qo;
  1116.  
  1117.         kol := kol + 1;
  1118.         x := x1;  y := y1;
  1119.  
  1120.         r := x1*x1 + y1*y1;
  1121.     UNTIL  (r > M) OR (kol > K);                { Until 'distance' > our infinity, or out of colors. }
  1122.  
  1123.     IF kol <= K THEN                                { Decide which zebra color to use. }
  1124.         IF ODD (kol) THEN PenPat (Black)
  1125.         ELSE  PenPat (White)
  1126.     ELSE                                                    { must be kol > K, ran out of colors. }
  1127.         PenPat (Black);
  1128.  
  1129.         { Move to the pixel we calculated for, then draw the pixel in right color.  This
  1130.             could be done by setting the bytes in pixel map directly, since we own the
  1131.             PixMap and the buffer. }
  1132.     MoveTo (fFracHeader.curCol, fFracHeader.curRow);
  1133.     Line (0,0);                                            { draw that 'pixel' in the right color }
  1134.  
  1135.  
  1136.         { up the counters to the next pixel location to do. }
  1137.     WITH  fFracHeader  DO BEGIN
  1138.         curCol := curCol + 1;                                    { up the column count. }
  1139.         drawRect := thePort^.portRect;                    { in case we finished a line. }
  1140.         IF curCol >= plotWidth  THEN  BEGIN            { did we run off end of row? }
  1141.                 { Have the line just calculated drawn to window.  DIV 4 since we are
  1142.                     4 times bigger offscreen. }
  1143.             drawRect.top := curRow DIV 4;
  1144.             drawRect.bottom := (curRow+1) DIV 4;
  1145.  
  1146.             curCol := 0;                                            { start on the next row. }
  1147.             curRow := curRow + 1;                            { and up the counter of the next row to do. }
  1148.         END;  { start at next row. }
  1149.  
  1150.     END;  { with  fFracHeader }
  1151.  
  1152.         { Check if we are done, and if so, set the flag to stop calculations.  Set the
  1153.             elapsed time counter in the header. }
  1154.     IF fFracHeader.curRow >= fFracHeader.plotHeight  THEN  BEGIN
  1155.         fFracHeader.done := TRUE;
  1156.         GetDateTime(currTime);
  1157.         fFracHeader.elapsedTime := currTime - fStartTime;
  1158.     END;
  1159.  
  1160.     IF NOT EqualRect (thePort^.portRect, drawRect) THEN
  1161.         fFracAppView.InvalidRect (drawRect);
  1162.  
  1163.     SetPort (currPort);
  1164.  
  1165.     { Now we have changed another point in the document.  We need to mark it as
  1166.         changed so we can save the document. }
  1167.     fChangeCount := fChangeCount + 1;
  1168.  
  1169. END;        { TFracAppDocument.DoIdle }
  1170.  
  1171.  
  1172. {-------------------------------  View  -------------------------------}
  1173.  
  1174.     { Initialize the view, basically set up the view object and clear the selection. }
  1175. PROCEDURE     TFracAppView.IFracAppView (itsDocument: TFracAppDocument;
  1176.         sizeOfView: Rect);
  1177.  
  1178. BEGIN
  1179.     fSelectionRect := gZeroRect;                    { no selection to start with. }
  1180.     fFracAppDocument := itsDocument;        { save off parent document for convenience. }
  1181.  
  1182.         { This view will be the full size of the screen since we have an offscreen
  1183.             bitMap as the view.  This will be clipped to fit the frame of the window.
  1184.             There is no parent view, and the horizontal and vertical are fixed.  The
  1185.             selection is to be shown, and is initially off. }
  1186.     IView(NIL, itsDocument, sizeOfView, sizeFixed, sizeFixed, TRUE, hlOff);
  1187.  
  1188. END;        { TFracAppView.IFracAppView }
  1189.  
  1190.  
  1191.     { Our routine to do the drawing of the fractal.  This is the display routine
  1192.         to take the data out of the offscreen buffer and whip it up to the window,
  1193.         as the current view.  The view is scaled down to screen res, giving an
  1194.         image that is close to page size, but with less resolution. }
  1195. PROCEDURE TFracAppView.Draw(area: Rect); OVERRIDE;
  1196.  
  1197. VAR        srcSize: REct;
  1198.  
  1199. BEGIN
  1200.     srcSize.top := area.top * 4;
  1201.     srcSize.left := area.left * 4;
  1202.     srcSize.bottom := area.bottom * 4;
  1203.     srcSize.right := area.right * 4 ;
  1204.     CopyBits ( fFracAppDocument.fDrawingPort^.portBits,
  1205.             thePort^.portBits, srcSize, area, srcCopy, Nil);
  1206. END;        { TFracAppView.Draw }
  1207.  
  1208.  
  1209.     { Handle the menu choice for New Fractal out of the Fractal Menu.  This makes a new
  1210.         Fractal based on the current selection.  It does it by calling on the application
  1211.         object to make a new document.  The communication to the DoInitialState is
  1212.         through the global variables. }
  1213. FUNCTION TFracAppView.DoMenuCommand(aCmdNumber: CmdNumber): TCommand;  OVERRIDE;
  1214.  
  1215. VAR        oldDeltaP,
  1216.             oldDeltaQ,
  1217.             oldRealMin,
  1218.             oldImagMin:    Extended;                    { for figuring new fractal area. }
  1219.  
  1220. BEGIN
  1221.     { Assume that we have no command to return, since none of our commands currently
  1222.         change the document. }
  1223.     DoMenuCommand := gNoChanges;            { no command object returned. }
  1224.  
  1225.     { Case off on the various menus.  Currently we have the new fractal item.
  1226.         Any out of that list are handled by Mr. MacApp and we pass it on. }
  1227.     CASE  aCmdNumber  OF
  1228.  
  1229.         kNewFractal:
  1230.             { If the option chosen was the New Fractal item, then we need to start
  1231.                 up a fresh one based on the selection rectangle.  This new fractal is
  1232.                 based on parts of the old one, since it is
  1233.                 a zoom in operation.  We make a new document/window/view
  1234.                 as if it were a New operation.  We then change the fields we need to in
  1235.                 that document to make it start calculating based on the selection from
  1236.                 the current view. }
  1237.  
  1238.             BEGIN
  1239.                 { Make a new document and initialize it to the base state.  If we fail in
  1240.                     opening it, we won't return here, the failure handler will kill it.  We
  1241.                     have nothing else to dispose of, so we don't make a CatchSignals here.
  1242.                     This will come out of permanent memory.  The aCmdNumber is so that
  1243.                     the new document knows it came from a zoom in operation.  Since this
  1244.                     is somewhat funky, we communicate to the other part of the program
  1245.                     with the global variables.  If nonzero, the code that makes a
  1246.                     new document will know to use these numbers in order to do the zoom.
  1247.                     This is less than completely desireable, but there are no good places
  1248.                     to override in order to get both the selection rectangle and the new
  1249.                     document objects. }
  1250.  
  1251.                     { The basic fractal has been set up.  We now need to change the calculation
  1252.                         area based on the current selection, in order to effect the zoom in. }
  1253.                 WITH  fFracAppDocument.fFracHeader DO BEGIN
  1254.                     oldDeltaP := deltaP;                            { from SELF, the old document. }
  1255.                     oldDeltaQ := deltaQ;
  1256.                     oldRealMin := realMin;
  1257.                     oldImagMin := imagMin;
  1258.                 END;
  1259.  
  1260.                 { calculate new min/max for real and imaginary parts based on how far
  1261.                     into the old fractal plane we were.  This is an extended calculation
  1262.                     since our plane is in extendeds.  We get the new locations of min and
  1263.                     max, and save them off.  We reset the deltaP or Q with SetUpConstants,
  1264.                     in order to force a 1:1 ratio, but we need all sides to determine
  1265.                     which one to force.  Scale up the selection area by 4 since it is in
  1266.                     screen res, not page res. }
  1267.                 gRealMin := oldRealMin + oldDeltaP * (fSelectionRect.top*4);
  1268.                 gImagMin := oldImagMin + oldDeltaQ * (fSelectionRect.left*4);
  1269.                 gRealMax := oldRealMin + oldDeltaP * (fSelectionRect.bottom*4);
  1270.                 gImagMax := oldImagMin + oldDeltaQ * (fSelectionRect.right*4);
  1271.  
  1272.                 gApplication.OpenNew (aCmdNumber);
  1273.             END;
  1274.  
  1275.         OTHERWISE
  1276.             DoMenuCommand := INHERITED  DoMenuCommand (aCmdNumber);    { next guy in chain. }
  1277.     END;        { CASE on aCmdNumber }
  1278.  
  1279. END;    { TFracAppView.DoMenuCommand }
  1280.  
  1281.  
  1282.     { Set up the New Fractal menus choice in Fractal Menu, based on selection. }
  1283. PROCEDURE TFracAppView.DoSetupMenus;  OVERRIDE;
  1284.  
  1285. BEGIN
  1286.     INHERITED DoSetupMenus;                            { Do mainline stuff first. }
  1287.  
  1288.         { If we have a non-zero selection, then we can enable the menu item to use
  1289.             it as the new fractal dimensions for this document. }
  1290.     Enable (kNewFractal, NOT EmptyRect (fSelectionRect));
  1291. END;    { TFracAppView.DoSetupMenus }
  1292.  
  1293.  
  1294.     { The way to handle mouse events in the content region of the view.  This will
  1295.         pass back the command object to handle tracking the mouse and creating a
  1296.         new selection in preparation for making a new fractal. }
  1297. FUNCTION  TFracAppView.DoMouseCommand(VAR downLocalPoint: Point;
  1298.                         VAR info: EventInfo;  VAR hysteresis: Point): TCommand;    OVERRIDE;
  1299.  
  1300. VAR        tracker:    TAreaSelector;
  1301.  
  1302. BEGIN
  1303.     New(tracker);                                        { make a new command object. }
  1304.     FailNIL(tracker);                                    { no memory, trash out. }
  1305.     tracker.IAreaSelector(SELF, downLocalPoint); { Initialize the command object. }
  1306.     DoMouseCommand := tracker;                { return it for later use. }
  1307. END;        { TFracAppView.DoMouseCommand }
  1308.  
  1309.  
  1310.     { Highlight the current selection rectangle if there is one.  This is drawn in scrCopy
  1311.         mode to make it stand out better when it is a final selection.  XOR is used for
  1312.         the rubberband, until mouseUp. }
  1313. PROCEDURE TFracAppView.DoHighLightSelection(fromHL, toHL: HLState); OVERRIDE;
  1314.  
  1315. VAR        selPatHandle:    PatHandle;
  1316.  
  1317. BEGIN
  1318.     IF toHL = hlOn THEN  BEGIN
  1319.         selPatHandle := GetPattern(kSelPattern);    { get the pattern we use. }
  1320.         IF selPatHandle <> NIL  THEN                         { If pattern available, use it. }
  1321.             PenPat (selPatHandle^^);                            { set pen pattern to our selection kind. }
  1322.         PenMode(srcCopy);                                        { copy mode on pattern selection. }
  1323.  
  1324.             { We have a selection, so go ahead and draw the selection rectangle. }
  1325.         FrameRect (fSelectionRect);                        { outline the frame of selection. }
  1326.     END;        { highlight turned on. }
  1327.  
  1328.         { Turning off the highlight, we need to remove the traces of the selection.
  1329.             To do this, redraw that rectangle. }
  1330.     IF toHL = hlOff THEN   Draw (fSelectionRect);    { ReDraw it to clear selection. }
  1331. END;        { TFracAppView.DoHighlightSelection }
  1332.  
  1333.  
  1334. {-------------------------------  Command  -------------------------------}
  1335.  
  1336.     { Initialize the selector object itself.  Sets up the normal fields. }
  1337. PROCEDURE TAreaSelector.IAreaSelector(ownerView: TFracAppView; startPt: Point);
  1338.  
  1339. BEGIN
  1340.     ICommand(cMouseCommand);                    { initialize normal parts of command }
  1341.  
  1342.     fCausesChange := FALSE;                            { just selection, not changing document. }
  1343.     fCanUndo := FALSE;                                    { therefore, no Undo of no change. }
  1344.     fConstrainsMouse := TRUE;                        { do the constrain to match to screen. }
  1345.     fOwnerView := ownerView;                        { save the view for use in tracking. }
  1346. END;    { TAreaSelector.IAreaSelector }
  1347.  
  1348.  
  1349.     { Track the mouse while the button is down.  This is overridden so we can leave
  1350.         the command object as not having changed, ie. so we can pass back the
  1351.         gNoChanges as the last step since this is not an undoable operation.  It
  1352.         doesn't change the view, so we don't need to DoIt or Commit. }
  1353. FUNCTION  TAreaSelector.TrackMouse(aTrackPhase: TrackPhase;
  1354.                         VAR anchorPoint, previousPoint, nextPoint: Point;
  1355.                         mouseDidMove: BOOLEAN): TCommand;  OVERRIDE;
  1356.  
  1357. VAR        selPatHandle:        PatHandle;
  1358.  
  1359. BEGIN
  1360.     TrackMouse := SELF;                                { Assume we are not in release phase. }
  1361.  
  1362.     CASE aTrackPhase OF
  1363.         trackPress:
  1364.         BEGIN
  1365.             fOwnerView.DoHighLightSelection (hlOn, hlOff);        { turn off old selection if any. }
  1366.             fOwnerView.fSelectionRect := gZeroRect;                { clear rect, there isn't one. }
  1367.         END;
  1368.  
  1369.         trackRelease:
  1370.         BEGIN
  1371.             Pt2Rect(anchorPoint, nextPoint, fOwnerView.fSelectionRect);
  1372.             fOwnerView.DoHighlightSelection (hlOff, hlOn);        { leave on selection. }
  1373.             TrackMouse := gNoChanges;
  1374.         END;
  1375.     END;    { Case on aTrackPhase }
  1376. END;    { TAreaSelector.TrackMouse }
  1377.  
  1378.  
  1379.     { Track the mouse giving the feedback of a different rectangle kind.  This is so
  1380.         we can use the selection pattern to give a preferred rectangle.  The selection
  1381.         pattern comes out of temporary memory so as to not fail needlessly. }
  1382. PROCEDURE TAreaSelector.TrackFeedback(anchorPoint, nextPoint: Point;
  1383.                         turnItOn, mouseDidMove: BOOLEAN);  OVERRIDE;
  1384.  
  1385. VAR        selBoy:                    Rect;
  1386.             selPatHandle:        PatHandle;
  1387.  
  1388. BEGIN
  1389.     IF mouseDidMove THEN
  1390.         BEGIN {the pen is already in patXOR mode, black, one wide}
  1391.             selPatHandle := GetPattern(kSelPattern);    { get the pattern we use. }
  1392.             IF selPatHandle <> NIL  THEN                        { use our pattern if available. }
  1393.                 PenPat (selPatHandle^^);                            { set pen pattern to our selection kind. }
  1394.  
  1395.             Pt2Rect(anchorPoint, nextPoint, selBoy);
  1396.             FrameRect(selBoy);
  1397.         END;
  1398. END;    { TAreaSelector.TrackFeedback }
  1399.  
  1400.  
  1401.     { Constrain the mouse to a rectangle that is the same proportion as the screen, so
  1402.         we can make the selection match better without having to guess at the length
  1403.         or width, or scaling the chosen rect to fit the screen.  Small piece chosen will
  1404.         blow up to fit easily.  This will make it easier to choose a selection that
  1405.         gives a 1:1 aspect ratio.  This also chooses which direction the mouse has
  1406.         moved, deciding which is larger in order to decide the direction to constrain. }
  1407. PROCEDURE TAreaSelector.TrackConstrain(anchorPoint, previousPoint: Point;
  1408.                             VAR nextPoint: Point);  OVERRIDE;
  1409.  
  1410. VAR        newWidth, newHeight:        LongInt;
  1411.             mouseRatio, plotRatio:        Real;
  1412.             constrainRect:                    Rect;
  1413.  
  1414.     PROCEDURE  ChangeWidth;
  1415.     BEGIN
  1416.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1417.                 { Get the new width as a positive number, a displacement that is constrained. }
  1418.             newWidth := Round (ABS (nextPoint.v - anchorPoint.v) * plotWidth / plotHeight);
  1419.                 { Decide which quadrant we are in, moving the right direction. }
  1420.             IF nextPoint.h < anchorPoint.h  THEN newWidth := -newWidth;
  1421.                 { Actually change the final point to pass back. }
  1422.             nextPoint.h := anchorPoint.h + newWidth;                        { add offset to get new pt. }
  1423.         END;
  1424.     END;
  1425.  
  1426.     PROCEDURE  ChangeHeight;
  1427.     BEGIN
  1428.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1429.             newHeight := Round (ABS (nextPoint.h - anchorPoint.h) * plotHeight / plotWidth);
  1430.             IF nextPoint.v < anchorPoint.v  THEN newHeight := -newHeight;
  1431.             nextPoint.v := anchorPoint.v + newHeight;                    { add offset to get new pt. }
  1432.         END;
  1433.     END;
  1434.  
  1435.     PROCEDURE  PinPoint;                    { Pin the rectangle to the edge of the view Rect. }
  1436.     BEGIN
  1437.         WITH  fOwnerView.fExtentRect  DO BEGIN
  1438.             SetRect(constrainRect, left, top, right, bottom);
  1439.             nextPoint := Point (PinRect(constrainRect, nextPoint));
  1440.         END;
  1441.     END;
  1442.  
  1443. BEGIN
  1444.     WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1445.         mouseRatio := ABS ((nextPoint.h - anchorPoint.h)/(nextPoint.v - anchorPoint.v));
  1446.         plotRatio := plotWidth/plotHeight;
  1447.  
  1448.             { The deltaX, deltaY can be thought of as a rect too.  If the ratio of sides on
  1449.                 that rect (width/height) is greater than the ratio of width/height of the
  1450.                 plot rectangle, then we need to grow the height of the rect.  If it is less,
  1451.                 we need to grow the width.  This is a ratio of sides to decide which way
  1452.                 to grow.  We grow to make the new rect still touch the mouse position.
  1453.                 It can be thought of as the rectangle being thicker than tall wanting to
  1454.                 grow the tall part in a constrained way, and the corollary for the width. }
  1455.         IF mouseRatio > plotRatio  THEN BEGIN        { constrain height to new value. }
  1456.                 ChangeHeight;
  1457.                 PinPoint;
  1458.                 ChangeWidth;
  1459.             END
  1460.         ELSE        BEGIN        { constrain width to new value. }
  1461.                 ChangeWidth;
  1462.                 PinPoint;
  1463.                 ChangeHeight;
  1464.             END;
  1465.     END;    { With }
  1466. END;    { TAreaSelector.TrackConstrain }
  1467.  
  1468.  
  1469. {-------------------------------  TFracAppPrintHandler  --------------------------}
  1470.  
  1471. { A print handler for doing 300 dpi printing, bypassing the standard print handler for
  1472.     it.  Uses the port created for calculating the fractal, and prints it using PrGeneral
  1473.     commands.  It is a print handler object so we get the fields and methods of the
  1474.     printing object.  Since it is an event handler, it will handle the menu events for
  1475.     the print operation, as well as enable their use. }
  1476.  
  1477.     { Init the print handler, setting up the fields in the handler.  Also attach it to the
  1478.         view and document so it knows who to call to set up the printing stuff.  }
  1479. PROCEDURE     TFracAppPrintHandler.IFracAppPrintHandler (itsView: TFracAppView);
  1480. BEGIN
  1481.     IPrintHandler(itsView);
  1482.     fOwningView := itsView;
  1483.     IF itsView <> NIL THEN  fView.AttachPrintHandler(SELF);
  1484.     itsView.fFracAppDocument.fDocPrintHandler := SELF;
  1485. END;
  1486.  
  1487.     { Enable the printing commands that this object handles.  Enabled when there
  1488.         is a document open. }
  1489. PROCEDURE TFracAppPrintHandler.DoSetupPrintingMenus;  OVERRIDE;
  1490. BEGIN
  1491.     INHERITED DoSetupMenus;                                    { Do mainline stuff first. }
  1492.     IF fView <> NIL  THEN  Enable (cPrint, TRUE);
  1493. END;
  1494.  
  1495.     { Do the print operation.  This is the code to
  1496.         actually send the data to the print manager and set it up using the highest
  1497.         resolution that the printer supports.  It is general purpose and will work on
  1498.         any printer that supports PrGeneral.  Part of this code was stolen from the
  1499.         MacApp StdPrintHandler in order to conform with the techniques used there. 
  1500.         There is no good place to override the StdPrintHandler in order to be able to
  1501.         call PrGeneral, which is why we are replacing the entire handler. }
  1502. FUNCTION TFracAppPrintHandler.Print(itsCmdNumber: CmdNumber; 
  1503.                     VAR proceed: BOOLEAN): TCommand; OVERRIDE;
  1504.  
  1505. VAR    fractalPort:        GrafPtr;
  1506.         thePrintRec:    THPrint;
  1507.         theGetRslBlk:    TGetRslBlk;
  1508.         theSetRslBlk:    TSetRslBlk;
  1509.         thePrintPort:    TPPrPort;
  1510.         thePrStats:        TPrStatus;
  1511.         fi:                     FailInfo;
  1512.         PrIsOpen:        Boolean;
  1513.         DocIsOpen:        Boolean;
  1514.         err:                    OSErr;
  1515.         
  1516.     PROCEDURE DeathPrint (error: OSErr; message: LONGINT);
  1517.  
  1518.     BEGIN
  1519.         IF DocIsOpen THEN  PrCloseDoc(thePrintPort);
  1520.         
  1521.             { Set the error code to what the print manager really wanted to say.  This is
  1522.                 to avoid messages that have already been displayed by the Print Manager.  This
  1523.                 is done after closing the document to be sure we have the final error result. }
  1524.         error := PrError;
  1525.         
  1526.         IF PrIsOpen THEN  PrClose;
  1527.         DisposHandle(Handle(thePrintRec));
  1528.         
  1529.             { Some errors should be changed to a message more meaningful. }
  1530.         IF (error = fnfErr) OR (error = resFNotFound) THEN  
  1531.             error := errNoPrintDrvr;
  1532.         IF error = resNotFound  THEN
  1533.             error := kBadPrinter;
  1534.             
  1535.             { If it was a user abort, we don't want to put up an alert. }
  1536.         IF error = iprAbort THEN
  1537.             Failure(noErr, 0);
  1538.         
  1539.             {Certain Print Manager errors should not result in any alert,
  1540.                 since the Print Manager will have already put one up.}
  1541.         IF (error >= -8160) AND (error <= -8150) THEN
  1542.             Failure(0, msgPrintFailed);
  1543.             
  1544.             {Add the name of the document to the error alert to make it easy to read.}
  1545.         IF message = 0 THEN
  1546.             gErrorParm3 := fView.fDocument.fTitle;
  1547.         FailNewMessage(error, message, msgPrintFailed);
  1548.     END;    { DeathPrint }
  1549.  
  1550. BEGIN
  1551.     Print := gNoChanges;                                            { Not undoable operation. }
  1552.     proceed := FALSE;                                                { If we get an error or cancel out. }
  1553.  
  1554.     CASE  itsCmdNumber  OF
  1555.         cPrint, cFinderPrint:  BEGIN
  1556.                 { They wish to print.  This is done by our special print handler, which overrides the
  1557.                     standard one.  It will print after setting up the special resolution using the PrGeneral
  1558.                     call. }
  1559.             fractalPort := fOwningView.fFracAppDocument.fDrawingPort;
  1560.             PrIsOpen := FALSE;
  1561.             DocIsOpen := FALSE;
  1562.             
  1563.             thePrintRec := THPrint(NewHandle(SIZEOF(TPrint)));
  1564.             FailNil(thePrintRec);
  1565.  
  1566.             CatchFailures(fi, DeathPrint);                { any failures, must be cleaned up. }
  1567.  
  1568.             PrOpen;
  1569.             FailOSErr (PrError);                            { An error here is driver not found. }
  1570.             
  1571.             PrIsOpen := TRUE;                                    { set our flag for error recovery. }
  1572.  
  1573.                 { Initialize the print record with default values since we don't supply a 
  1574.                     page setup operation. }
  1575.             PrintDefault(thePrintRec);
  1576.  
  1577.                 { Get the possible resolutions for the current device... }
  1578.             theGetRslBlk.iOpCode := getRslDataOp;
  1579.             PrGeneral(@theGetRslBlk);
  1580.             FailOSErr (PrError);                            { An error here means driver doesn't have resource. }
  1581.                 { An error here means driver doesn't support resolutions.  Since there is no predefined
  1582.                     error message for this, we will turn it into our own error code so that the right code
  1583.                     from the 'errs' resource will map to the new STR# error message. }
  1584.             IF theGetRslBlk.iError <> noErr THEN FailOSErr (kBadPrinter);
  1585.  
  1586.                 { Set the device to the maximum possible resolution.  This is a little funky, since we
  1587.                     assume that the highest resolution is the last element of the table.  This is valid
  1588.                     currently and sensible, but not explicitly documented. }
  1589.             WITH theGetRslBlk, theSetRslBlk DO BEGIN
  1590.                 theSetRslBlk.iOpCode := setRslOp;
  1591.                 hPrint := thePrintRec;
  1592.                 iXRsl := rgRslRec[iRslRecCnt].iXRsl;
  1593.                 iYRsl := rgRslRec[iRslRecCnt].iYRsl;
  1594.             END;
  1595.             PrGeneral(@theSetRslBlk);
  1596.             FailOSErr (PrError);
  1597.             IF theSetRslBlk.iError <> noErr THEN FailOSErr (kBadPrinter);
  1598.  
  1599.                 { Call the job dialog to see what the user wants.  If they click OK, we continue.  If not,
  1600.                     we error out with the noErr error code to clean up, but it won't show an alert.  This
  1601.                     is done to avoid the confusing nested IF structures so common in printing code...}
  1602.             IF NOT PrJobDialog(thePrintRec) THEN Failure (noErr, 0);
  1603.             
  1604.             thePrintPort := PrOpenDoc(thePrintRec, NIL, NIL);
  1605.                 { Handle the special case of out of disk space, by failing with right message. }
  1606.             IF PrError =  -1 {iPrSavPFil} THEN Failure(errSpooling, 0);
  1607.             
  1608.             DocIsOpen := TRUE;                                { Set so error routine can close the right pieces. }
  1609.  
  1610.             PrOpenPage(thePrintPort, NIL);
  1611.             FailOSErr (PrError);
  1612.  
  1613.                 { Do the actual drawing to the printer port. }
  1614.             CopyBits (fractalPort^.portBits, thePrintPort^.GPort.portBits,
  1615.                     fractalPort^.portRect, fractalPort^.portRect, srcCopy, NIL);
  1616.             FailOSErr (PrError);
  1617.  
  1618.             PrClosePage(thePrintPort);
  1619.                 { Handle the special case of out of disk space, by failing with right message. }
  1620.             IF PrError =  -1 {iPrSavPFil} THEN Failure(errSpooling, 0);
  1621.  
  1622.             PrCloseDoc(thePrintPort);
  1623.                 { Handle the special case of out of disk space, by failing with right message. }
  1624.             IF PrError =  -1 {iPrSavPFil} THEN Failure(errSpooling, 0);
  1625.  
  1626.             DocIsOpen := FALSE;
  1627.  
  1628.                 { Image the spool file if necessary. }
  1629.             IF  thePrintRec^^.prJob.bJDocLoop = bSpoolLoop  THEN
  1630.                 PrPicFile(thePrintRec, NIL, NIL, NIL, thePrStats);
  1631.                 { Handle the special case of out of disk space, by failing with right message. }
  1632.             IF PrError =  -1 {iPrSavPFil} THEN Failure(errSpooling, 0);
  1633.  
  1634.                 { We are past the potential failures.  Clear the error signal. }
  1635.             Success (fi);
  1636.             PrClose;
  1637.             DisposHandle(Handle(thePrintRec));
  1638.         END;        { cPrint }
  1639.     END;        { CASE aCmdNumber }
  1640.             
  1641.         { If we succeeded, we return here, and can set the flag to continue printing more documents. }
  1642.     proceed := TRUE;
  1643. END;    { TFracAppPrintHandler.Print }
  1644.  
  1645.  
  1646.     { We need to have a DoPrinting method to handle the menu command.
  1647.         This does nothing more than pass the buck to the Print method, but we need the
  1648.         override to have the proper flow of control. }
  1649. FUNCTION  TFracAppPrintHandler.DoPrintingCommand(aCmdNumber: CmdNumber): TCommand;  OVERRIDE;
  1650. VAR
  1651.     proceed:    Boolean;
  1652. BEGIN
  1653.     DoPrintingCommand := Print (aCmdNumber, proceed);
  1654. END;    { DoPrintingCommand }
  1655.  
  1656.  
  1657. {$POP}        { Restore the compiler state. }
  1658.